/*
 * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.identitymaps;

import java.util.Vector;

import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.testing.tests.clientserver.Server;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.testing.models.bigbad.*;

/**
 * This test is set up to have one thread build a BigBadObject instance, while multiple other threads
 * attempt to access the same instance from the cache.  The test fails if any of the threads access
 * an incomplete instance of the BigBadObject object - where it has been only partially built.  Only
 * the "number02" attribute is checked, as all mapping weights are changed so this mapping is
 * built last.  Built to test bug 4772232
 */
public class ConcurrentReadBigBadObjectTest extends AutoVerifyTestCase {
    public ConcurrentReadBigBadObjectTest() {
    }

    BigBadObject referenceObject;
    protected int numOftries = 300;
    protected int numOfThreads = 10;
    protected boolean failed;
    protected Server server;
    protected int OrigDirectMapWeight;


    public void setup() throws Exception {

        DatabaseLogin login;
        login = (DatabaseLogin)getSession().getLogin().clone();
        server = new Server(login);
        server.serverSession.setLogLevel(getSession().getLogLevel());
        server.serverSession.setLog(getSession().getLog());


        server.copyDescriptors(getSession());
        ClassDescriptor d = (server.serverSession).getClassDescriptor(BigBadObject.class);
        DatabaseMapping m;

        Vector v = d.getMappings();
        int mappings = v.size();
        int i = 0;
        while (i < mappings) {
            m = (DatabaseMapping)v.get(i);
            m.setWeight(new Integer(Integer.MAX_VALUE - 1));
            i++;
        }

        m = d.getMappingForAttributeName("number02");
        m.setWeight(new Integer(Integer.MAX_VALUE));

        server.login();
        server.serverSession.setLogLevel(getSession().getLogLevel());
        server.serverSession.setLog(getSession().getLog());
    }

    public void reset() throws Exception {
        server.logout();
    }

    public void test() {
        failed = false;

        referenceObject = (BigBadObject)this.getSession().readObject(BigBadObject.class);
        Reader[] threadList = new Reader[numOfThreads];

        for (int i = 0; i < numOfThreads; ) {
            threadList[i] = new Reader(referenceObject, server.serverSession.acquireClientSession(), ++i);
        }

        for (int i = 0; i < numOfThreads; i++) {
            threadList[i].start();
        }

        try {
            for (int i = 0; i < numOfThreads; i++) {
                threadList[i].join();
            }
        } catch (InterruptedException ex) {
        }
        int count = 0;
        while (!failed && count < numOfThreads) {
            if (threadList[count].exception != null) {
                throw threadList[count].exception;
            }
            failed = threadList[count].hadError();
            count++;
        }
        if (failed) {
            throw new TestErrorException("Test failed, getFromIdentityMap returned an object before it was finished being built");
        }

    }


    /**
     * Threads to read/access the cache numOftries times.  Only thread 1 will initialize the cache and
     * read the BigBadObject object instance.
     */
    private class Reader extends Thread {
        protected BigBadObject referenceObject, readObject;
        protected Session session;
        public int thread;
        public int counter;
        public RuntimeException exception;

        protected boolean experienceError = false;

        public Reader(BigBadObject object, Session session, int thread) {
            this.referenceObject = object;
            this.session = session;
            this.thread = thread;
            counter = 0;
        }

        public void run() {
            try {
                counter = 0;
                while (!experienceError && counter < numOftries) {
                    if (thread == 1) {
                        session.getIdentityMapAccessor().initializeIdentityMap(BigBadObject.class);
                        readObject = (BigBadObject)session.readObject(referenceObject);
                    } else {
                        readObject = (BigBadObject)session.getIdentityMapAccessor().getFromIdentityMap(referenceObject);
                    }
                    if ((readObject != null) &&
                        ((readObject.number02 == null) || (!readObject.number02.equals(referenceObject.number02)))) {
                        this.experienceError = true;
                    }
                    counter++;
                }
            } catch (RuntimeException ex) {
                this.experienceError = true;
                this.exception = ex;
            }
        }

        public boolean hadError() {
            return experienceError;
        }
    }
}
